package org.apache.sad.core.framework.logging.log4j;

import java.text.DateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.Iterator;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Layout;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.spi.LoggingEvent;

public class DelimitedLayout extends SimpleLayout {
	private static final String BEAN_EXCEPTION_MESSAGE = "Could not find bean property defined in DelimitedLayout.";
	private static final DateFormat dateFormat = new SimpleDateTimeDateFormat(TimeZone.getDefault());


	private static final String DEFAULT_DELIMITER = ",";
	private static final String PIPE_DELIMITER = "|";
	

	private static final String ESCAPE_CHARACTER = "\"";

	private static final String NEW_LINE = "\n";
		
	private String delimiter;

	private String lineBreak;
	
	private String headerPattern;

	private String recordPattern;

	public DelimitedLayout() {
		super();	
	}

	protected String escapeDelimiter(String stringToEscape) {
		String escapedString = StringUtils.EMPTY;

		if (StringUtils.isNotEmpty(stringToEscape)) {
			if (StringUtils.contains(stringToEscape, this.getDelimiter())) {
				StringBuilder stringBuilder = new StringBuilder(3);
				stringBuilder.append(ESCAPE_CHARACTER);
				stringBuilder.append(stringToEscape);
				stringBuilder.append(ESCAPE_CHARACTER);
				escapedString = stringBuilder.toString();
			}
			else {
				escapedString = stringToEscape;
			}
		}

		return escapedString;
	}

	protected String removeLineFeeds(String s) {

		String newString = StringUtils.EMPTY;

		if (StringUtils.isNotEmpty(s)) {
			s = s.replace(LINE_SEP, " ");
			s = s.replace(NEW_LINE, " ");
			
			if (!StringUtils.isEmpty(this.lineBreak))
				s = s.replace(lineBreak, " ");
			
			newString = s;			
		}

		return newString;
	}

	@Override
	public String format(LoggingEvent event) {

		StringBuilder stringBuilder = new StringBuilder(2);
		stringBuilder.append(this.render((Object) event.getMessage()));
		stringBuilder.append(Layout.LINE_SEP);

		return stringBuilder.toString();
	}

	@SuppressWarnings("unchecked")
	protected String formatMap(Map map) {
		StringBuffer listString = new StringBuffer(StringUtils.EMPTY);
		if (map != null && map.size() > 0) {
			Iterator itr = map.keySet().iterator();
			while (itr.hasNext()) {
				String key = (String)itr.next();
				listString.append(key);
				listString.append("=");
				listString.append(map.get(key).toString());
				listString.append(PIPE_DELIMITER);
			}
			listString.setLength(listString.length() - 1);
		}
		return listString.toString();
	}
	
	protected String formateDateIfNotNull(Date date) {
		String dateString = StringUtils.EMPTY;

		if (date != null) {
			dateString = dateFormat.format(date);
		}

		return dateString;
	}

	public String getDelimiter() {
		return delimiter;
	}

	protected String getEmptyStringIfNull(Object objectToCheck) {
		// Declare variable(s)
		String stringToReturn = StringUtils.EMPTY;

		// Check if the string is null
		if (objectToCheck != null) {
			stringToReturn = objectToCheck.toString();
		}

		return stringToReturn;
	}

	@Override
	public String getHeader() {

		String header = super.getHeader();

		if (StringUtils.isNotEmpty(this.getHeaderPattern()) && StringUtils.isNotEmpty(this.getDelimiter())) {

			String headerPatternWithDelimiter = StringUtils.replace(this.getHeaderPattern(), DEFAULT_DELIMITER, this.getDelimiter());

			StringBuilder stringBuilder = new StringBuilder(2);
			stringBuilder.append(headerPatternWithDelimiter);
			stringBuilder.append(Layout.LINE_SEP);
			header = stringBuilder.toString();
		}

		return header;
	}

	public String getHeaderPattern() {
		return headerPattern;
	}

	public String getRecordPattern() {
		return recordPattern;
	}

	@SuppressWarnings("unchecked")
	protected String render(Object bean) {
		String render = null;

		if (bean != null && StringUtils.isNotEmpty(this.getDelimiter()) && StringUtils.isNotEmpty(this.getRecordPattern())) {
			String[] fields = StringUtils.split(this.getRecordPattern(), this.getDelimiter());

			int elements = fields.length * 2 - 1;

			StringBuilder stringBuilder = new StringBuilder(elements);

			for (int counter = 0; counter < fields.length; counter++) {
				Object propertyValue = null;
				try {
					propertyValue = PropertyUtils.getProperty(bean, fields[counter]);
				} catch (Exception e) {
					throw new RuntimeException(BEAN_EXCEPTION_MESSAGE, e);
				}

				String propertyValueAsString = null;

				if (propertyValue instanceof Date) {
					propertyValueAsString = this.formateDateIfNotNull((Date) propertyValue);
				}
				else if (propertyValue instanceof Map) {
					propertyValueAsString = this.formatMap((Map)propertyValue);
				} else {
					propertyValueAsString = this.getEmptyStringIfNull(propertyValue);
				}

				// Remove line feeds
				propertyValueAsString = this.removeLineFeeds(propertyValueAsString);
				
				// Escape the property value
				String escapedString = this.escapeDelimiter(propertyValueAsString);

				// Append the property value
				stringBuilder.append(escapedString);

				// Check whether or not to append the delimiter
				if (counter < fields.length - 1) {
					stringBuilder.append(this.getDelimiter());
				}
			}

			// Get the built string
			render = stringBuilder.toString();
		}

		return render;
	}

	public void setDelimiter(String delimiter) {
		this.delimiter = delimiter;
	}

	public void setHeaderPattern(String headerPattern) {
		this.headerPattern = headerPattern;
	}

	public void setRecordPattern(String recordPattern) {
		this.recordPattern = recordPattern;
	}

	public String getLineBreak() {
		return lineBreak;
	}

	public void setLineBreak(String lineBreak) {
		this.lineBreak = lineBreak;
	}
}